package com.github.monkeywie.proxyee.log;


import io.netty.util.concurrent.DefaultThreadFactory;
import io.netty.util.internal.StringUtil;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.BiFunction;

public class LogRecorder {
	public static final String lineSeparator = System.getProperty("line.separator");
	private OutputStream output=null;
	protected Level lowestLevel=Level.INFO;
	protected BiFunction<Level,String,String> contentFormatFun=LogRecorderFactory.buildFullContentFormatFun();
	private boolean isAsync=true;
    private static final ExecutorService executor = Executors.newFixedThreadPool(1, new DefaultThreadFactory("logRecorder-pool",true));


	public LogRecorder(){
		super();
	}
	public LogRecorder(OutputStream output){
		this.output=output;
	}
	
	public LogRecorder(OutputStream output,Level lowestLevel){
		this.output=output;
		this.lowestLevel=lowestLevel;
	}
	public OutputStream getOutput() {
		return output;
	}
	public void setOutput(OutputStream output) {
		this.output = output;
	}
	public Level getLowestLevel() {
		return lowestLevel;
	}
	public void setLowestLevel(Level lowestLevel) {
		this.lowestLevel = lowestLevel;
	}
	public BiFunction<Level, String, String> getContentFormatFun() {
		return contentFormatFun;
	}
	public void setContentFormatFun(BiFunction<Level, String, String> contentFormatFun) {
		this.contentFormatFun = contentFormatFun;
	}
	public void log(Level level,String content,Object...objects) {
		if(content==null){
			return;
		}
		if(isEnabled(level)){
			try {
				if(output!=null){
					String outContent = contentFormatFun.apply(level, convert(content, objects));
					if(outContent==null){
						return;
					}
					byte[] logBytes=outContent.getBytes();
					if(isAsync) {
						executor.submit(()->{
							try {
								output.write(logBytes);
							} catch (Exception e) {
								e.printStackTrace();
							}
						});
					}else {
						output.write(logBytes);	
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	public final void trace(String content,Object...arguments){
		log(Level.TRACE,content,arguments);
	}
	
	public final void debug(String content,Object...arguments){
		log(Level.DEBUG,content,arguments);
	}
	
	public final void info(String content,Object...arguments){
		log(Level.INFO,content,arguments);
	}
	
	public final void warn(String content,Object...arguments){
		log(Level.WARN,content,arguments);
	}
	
	public final void error(String content,Object...arguments){
		log(Level.ERROR,content,arguments);
	}

	public void error(String content,Throwable e,Object...arguments){
		content=convert(content,arguments)+lineSeparator + getExceptionInfo(e,30000);
		log(Level.ERROR,content,null);
	}
	
	public final void fatal(String content,Object...arguments){
		log(Level.FATAL,content,arguments);
	}

	public final void fatal(String content,Throwable e,Object...arguments){
		content=convert(content,arguments)+lineSeparator + getExceptionInfo(e,30000);
		log(Level.FATAL,content,null);
	}

	public final boolean isEnabled(Level level) {
		return level.code>=lowestLevel.code;
	}

	/**
	 * 将参数替换至占位符"{}"的位置
	 * @param content
	 * @param arguments
	 * @return
	 */
	private static String convert(String content,Object...arguments) {
		if(arguments==null||arguments.length==0){
			return content;
		}
		StringBuilder sb=new StringBuilder();
		if(content!=null) {
			char[] charArray=content.toCharArray();
			int count=0;
			boolean buried=false;
			for(int i=0;i<charArray.length;i++) {
				if(charArray[i]=='\\' && !buried) {
					buried=true;
				}else if(buried) {
					sb.append(charArray[i]);
					buried=false;
				}else if(charArray[i]=='{' && charArray[i+1]=='}') {
					if(arguments.length>count) {
						if(arguments[count]!=null){
							sb.append(arguments[count].toString());
						}
					}else {
						sb.append("{}");
					}
					count++;
					i++;
				}else {
					sb.append(charArray[i]);
				}
			}
		}
		return sb.toString();
	}


	private static String getExceptionInfo(Throwable e,int maxLength) {
		StringWriter sw = null;
		PrintWriter pw = null;
		try {
			sw = new StringWriter();
			pw = new PrintWriter(sw);
			e.printStackTrace(pw);
			String result = sw.toString();
			if(!StringUtil.isNullOrEmpty(result) && result.length() > maxLength) {
				result = result.substring(0,maxLength);
			}
			return result;
		} finally {
			if (null != sw) {
				try {
					sw.close();
				} catch (IOException ex) {
					ex.printStackTrace();
				}
			}
			if (null != pw) {
				pw.close();
			}
		}
	}

	public static enum Level{
		FATAL(6),
		ERROR(5),
		WARN(4),
		INFO(3),
		DEBUG(2),
		TRACE(1);
		private int code;
		private Level(int code){
			this.code=code;
		}
		int getCode(){
			return code;
		}
	}

}
